package evol;

import java.util.ArrayList;
import java.util.Random;

import nodes.Node;
import nodes.TreeManipulator;
import nodes.typeconst.ConstantStrategy;
import func.FunctionFactory;
import func.rank.RankFunction;

public class Evolver<T> {
	
	private int pc;
	private int popsize;
	private RankFunction<T> rank;
	private int maxgen;
	private double mutationrate, breedingrate, pexp, pnew;
	private FunctionFactory<T> fac;
	private ConstantStrategy<T> strat;
	private TreeOutputter out;
	
	private final int DEPTH_OF_RANDOM_TREE = 4;
	private final double ERROR = 1E-10;
	
	public Evolver(RankFunction<T> rank, FunctionFactory<T> fac, 
			ConstantStrategy<T> strat, TreeOutputter out) {
		this(6, 500, rank, Integer.MAX_VALUE, 0.1, 0.4, 0.8, 0.1, fac, strat, out);
	}

	public Evolver(int pc, int popsize, RankFunction<T> rank, int maxgen,
			double mutationrate, double breedingrate, double pexp, double pnew,
			FunctionFactory<T> fac, ConstantStrategy<T> strat, TreeOutputter out) {
		this.pc = pc;
		this.popsize = popsize;
		this.rank = rank;
		this.maxgen = maxgen;
		this.mutationrate = mutationrate;
		this.breedingrate = breedingrate;
		this.pexp = pexp;
		this.pnew = pnew;
		this.fac = fac;
		this.strat = strat;
		this.out = out;
	}
	
	public Node<T> evolve() {
		TreeManipulator<T> tree = new TreeManipulator<T>(
				mutationrate, breedingrate, fac.createFunctionGroup(), strat);
		Random ran = new Random();
		ArrayList<Node<T>> population = new ArrayList<Node<T>>();
		//Node<T>[] population = (Node<T>[]) new Object[popsize];

		for(int i = 0; i < popsize; i++)
			population.add(tree.makeRandomTree(pc, DEPTH_OF_RANDOM_TREE));
		
		for(int j = 0; j < maxgen; j++) {
			rank.sortByRank(population);
			double dif = rank.applyScoreFunction(population.get(0));
			//System.out.println("Generation " + (j + 1) + ": " + dif);
			if(dif < ERROR)
				break;
			
			ArrayList<Node<T>> newpop = new ArrayList<Node<T>>();
			out.output(population, rank);
			//Node<T>[] newpop = (Node<T>[]) new Object[popsize];
			//population.get(0).accept(visit);
			
//			System.out.println("------ Top 10 -------");
//			for(int i = 0; i < 100; i++)
//				System.out.println(rank.applyScoreFunction(population.get(i + 1)));
//			System.out.println("-------- End --------");
			
			newpop.add(population.get(0)); 
			newpop.add(population.get(1));
			for(int i = 2; i < popsize; i++) {
				if(ran.nextDouble() > pnew)
					newpop.add(tree.mutate(
							tree.crossover(population.get(selectIndex()), 
									population.get(selectIndex()), true), pc));
				else
					newpop.add(tree.makeRandomTree(pc, DEPTH_OF_RANDOM_TREE));
			}
			
			population = newpop;
		}
			
		//population.get(0).accept(visit);
		return population.get(0);
	}
	
	private int selectIndex() {
		int i = (int) (Math.log(Math.random()) / Math.log(pexp));
		//System.out.println("-- Index: " + i);
		return i;
		//return (int) (Math.log(Math.random()) / Math.log(pexp));
	}
}
